#pragma once


#include <brpc/controller.h>
#include <brpc/server.h>
#include <cstdint>
#include <fstream>
#include <jsoncpp/json/value.h>
#include <log.h>
#include <memory>
#include <odb/mysql/database.hxx>
#include <sstream>
#include <string>
#include <sw/redis++/redis.h>
#include <vector>


#include "ProblemOperator.hpp"
#include "Util.hpp"
#include "code_templates.h"
#include "es.h"
#include "problem_redis.h"
#include "etcd.hpp"
#include "etcd_path.h"
#include "problem.pb.h"
#include "redis.h"
#include "mysql.h"
#include "brpc.h"
#include "compile.pb.h"
#include "compile.pb.cc"
#include "es_problem.h"

namespace lyt {
    class ProblemImpl : public problem::ProblemService{
    private:
        // mysql
        ProblemData::ptr _problem_mysql;
        // redis
        ProblemRedis::ptr _problem_redis;
        // es
        EsProblemData::ptr _esclient;
        // brpc (compile service)
        std::shared_ptr<brpc::Channel> _channel;
        std::shared_ptr<compile::CompileService_Stub> _compileservice_stub;
    public:
        using ptr = std::shared_ptr<ProblemImpl>;
        ProblemImpl(const std::shared_ptr<odb::mysql::database>& odb
                   ,const std::shared_ptr<sw::redis::Redis> redis
                   ,const esclient::ptr& esclient
                   ,const std::string& etcd_path)
                    :_problem_mysql(std::make_shared<ProblemData>(odb))
                    ,_problem_redis(std::make_shared<ProblemRedis>(redis))
                    ,_esclient(std::make_shared<EsProblemData>(esclient)
        ){
            _esclient->index();
            _channel = lyt::build_etcd(etcd_path, lyt::compile_path);
            _compileservice_stub = make_shared<compile::CompileService_Stub>(_channel.get());
        }
        void ProblemList(::google::protobuf::RpcController* controller,
                        const ::problem::ProblemListRequest* request,
                        ::problem::ProblemListResponse* response,
                        ::google::protobuf::Closure* done
        ){
            // 1. 验证token
            // 2. 在Redis中查询solvedStatus
            // 3. 获取对应的题目id后，使用es筛选条件，包括难度，页大小等
            // 4. 构造响应
            brpc::ClosureGuard done_guard(done);
            auto handler = [&](const bool success,const std::string& message=""){
                response->set_success(success);
                response->set_message(message);
                return;
            };
            auto build_result = [&](const std::pair<Json::Value,int>& result){
                for(const auto& hit : result.first){
                    const auto& source = hit["source"];
                    auto problem = response->add_problems();
                    problem->set_id(source[es_problem_id_key].asUInt());
                    problem->set_title(source[es_problem_title_key].asString());
                    problem->set_difficulty(source[es_problem_difficulty_key].asUInt());
                    // 通过率通过redis获取
                    float rate = _problem_redis->get_acceptance_rate(source[es_problem_id_key].asUInt());
                    if(rate < 0){
                        rate = _problem_redis->cal_acceptance_rate(source[es_problem_id_key].asUInt());
                    }
                    problem->set_acceptance_rate(rate);
                }
                response->set_total(result.second);
                return handler(true,"获取成功~");
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                controller->SetFailed("Invalid token");
                return handler(false,"获取题目列表失败，用户未登录");
            } 
            lyt::search search_cond(_esclient->getclient());
            std::string email = *opt;
            if(request->solvedstatus() == 0){
                // 全部题目，直接查ES
                // 1. 添加difficulty和searchQuery条件
                if(!request->searchquery().empty()){
                    search_cond.add_should_match(es_problem_title_key, request->searchquery());         
                }
                if(request->difficulty() > 0){
                    search_cond.add_must_match(es_problem_difficulty_key, to_string(request->difficulty()));
                }
                // 2. 分页
                int offset = (request->page()-1) * request->pagesize();
                // 如果offset为0，表示从第1条开始取，取pagesize个数据
                search_cond.set_from(offset).set_size(request->pagesize());
                // 3. 根据sortBy进行排序
                switch(request->sortby()){
                    case 1:
                        search_cond.add_sort(es_problem_id_key);
                        break;
                    case 2:
                        search_cond.add_sort(es_problem_difficulty_key);
                        break;
                    default:
                        search_cond.add_sort(es_problem_id_key, true);
                        break;
                }
                // 搜索
                std::pair<Json::Value,int> result = search_cond.run(es_problem_index);
                return build_result(result);
            }else {
                std::vector<uint32_t> ids;
                // 先去redis中查询，再拿查询出的id去es中查询
                if(request->solvedstatus() == 1){
                    // 已解决题目
                    ids = _problem_redis->get_solved_problems(email);
                }else if (request->solvedstatus() == 2) {
                    // 尝试题目
                    ids = _problem_redis->get_attempting_problems(email);
                }
                
                if(ids.empty()){
                    debuglog << "solved is empty!";
                    response->set_total(0);
                    handler(false,"暂无题目！"); 
                }
                search_cond.add_must_terms_integer(es_problem_id_key, ids);
                if(!request->searchquery().empty()){
                    search_cond.add_should_match(es_problem_title_key, request->searchquery());
                }
                if(request->difficulty() > 0){
                    search_cond.add_must_match(es_problem_difficulty_key, to_string(request->difficulty()));
                }
                int offset = (request->page()-1) * request->pagesize();
                search_cond.set_from(offset).set_size(request->pagesize());
                switch(request->sortby()){
                    case 1:
                        search_cond.add_sort(es_problem_id_key);
                        break;
                    case 2:
                        search_cond.add_sort(es_problem_difficulty_key);
                        break;
                    default:
                        search_cond.add_sort(es_problem_id_key, true);
                        break;
                }
                auto result = search_cond.run(es_problem_index);
                return build_result(result);
            }
        }
        void ProblemDetail(::google::protobuf::RpcController* controller,
                        const ::problem::ProblemDetailRequest* request,
                        ::problem::ProblemDetailResponse* response,
                        ::google::protobuf::Closure* done){
            // 1. 验证token
            // 2. 填充problemdetail
            // 3. 构造响应
            brpc::ClosureGuard done_guard(done);
            auto ReturnError  = [&](const std::string& err, const std::string& user_msg){
                controller->SetFailed(err);
                response->set_success(false);
                response->set_message(user_msg);
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                return ReturnError("Invalid token", "获取题目详情失败，用户未登录");
            }
            lyt::search search_cond(_esclient->getclient());
            auto result = search_cond.add_must_terms_integer(es_problem_id_key, {request->id()})
                                                                .run(es_problem_index);
            auto& source = result.first["source"];
            problem::ProblemDetail pd;
            if (source.isMember(es_problem_id_key) && source[es_problem_id_key].isUInt()) {
                pd.set_id(source[es_problem_id_key].asUInt());
            } else {
                return ReturnError("Missing id", "题目ID无效");
            }
            if (source.isMember(es_problem_title_key) && source[es_problem_title_key].isString()) {
                pd.set_title(source[es_problem_title_key].asString());
            } else {
                return ReturnError("Missing title", "没有对应题目");
            }
            if (source.isMember(es_problem_description_key) && source[es_problem_description_key].isString()) {
                pd.set_description(source[es_problem_description_key].asString());
            } else {
                return ReturnError("Missing description", "没有对应题目描述");
            }
            if (source.isMember(es_problem_difficulty_key) && source[es_problem_difficulty_key].isUInt()) {
                pd.set_difficulty(source[es_problem_difficulty_key].isUInt());
            } else {
                return ReturnError("Missing difficulty", "未查找到题目难度");
            }
            // 填充tags
            for(auto tag : source[es_problem_tags_key]){
                pd.add_tags(tag.asString());
            }
            // 填充模板映射
            // 返回的是google::protobuf::Map<std::string, std::string>与C++map<string,string>类型不兼容
            // *pd.mutable_templates() = lyt::map_;
            auto* template_maps = pd.mutable_templates();
            for(const auto& [lang,code] : lyt::map_){
                (*template_maps)[lang] = code;
            }
            // 填充测试用例(mysql中查找)
            std::vector<problem::TestCase> cases;
            if(!_problem_mysql->query_cases(request->id(), cases)){
                return ReturnError("Missing testcases", "无测试用例");
            }
            for(auto& case_:cases){
                auto tmp = pd.add_test_cases();
                *tmp = case_;
            }
            // 填充用例范围提示
            for(auto casehint : source[es_problem_testcasehint_key]){
                pd.add_test_case_hint(casehint.asString());
            }
            *response->mutable_problem() = pd;
            response->set_success(true);
            response->set_message("获取题目详情成功");
        }
        void Submit(::google::protobuf::RpcController* controller,
                        const ::problem::SubmitRequest* request,
                        ::problem::SubmitResponse* response,
                        ::google::protobuf::Closure* done){
            brpc::ClosureGuard done_guard(done);
            auto handler = [&](const bool success
                ,const std::string& message=""
                ,const std::string& accept=""
                ,const std::string& result = ""
                ,const std::string& code=""
                ,const std::string& language=""){
                response->set_success(success);
                response->set_message(message);
                response->set_accepted(accept);
                response->set_code(code);
                response->set_language(language);
            };
            auto insert_record_to_mysql = [&](
                const std::string& email,
                const std::string& stats,
                const std::string& result){
                ProblemSubmissionRecord record;
                record.user_id = email;
                record.problem_id = request->problem_id();
                record.submit_id = lyt::Generate_UUID::generate_uuid();
                record.accept = stats;
                record.result = result;
                record.language = request->language();
                record.time = lyt::GetTimeISO::gettimeiso();
                record.code = request->code();
                _problem_mysql->insert_submit_record(record);
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                controller->SetFailed("Invalid token!");
                return handler(false, "提交失败，用户未登录");
            }
            std::string email = *opt;
            if (request->code().empty()) {
                return handler(false, "提交代码为空");
            }
            if (request->language().empty()) {
                return handler(false, "请选择编程语言");
            }
            compile::CompileRequest req;
            compile::CompileResponse res;
            req.set_code(request->code());
            if(request->submit() == false){
                // 自测
                req.set_input(request->test_data());
                brpc::Controller cntl;
                _compileservice_stub->Compile(&cntl, &req, &res, nullptr);
                if(cntl.Failed()){
                    errorlog << "编译服务调用失败!";
                    return handler(false,"500 Error!");
                }
                if(res.status() == 0){
                    // 运行成功,自测直接返回即可
                    return handler(true,"运行成功","通过"
                        ,res.result(),request->code(),request->language());
                }else if(res.status() == 1){
                    // 内部错误
                    errorlog << "内存错误（读写文件失败！）";
                    return handler(false,"500 ERROR!");
                }else{
                    // 编译或运行报错
                    return handler(false,"运行失败","未通过"
                        ,res.err_msg(),request->code(),request->language());
                }
            }
            // 运行服务器测试用例
            std::vector<problem::TestCase> testcases;
            _problem_mysql->query_cases(request->problem_id(), testcases);
            int sum = testcases.size();
            int num = 1;
            for(const auto& testcase : testcases){
                // 循环调用编译服务,如果报错直接返回
                req.set_input(testcase.input());
                brpc::Controller cntl;
                _compileservice_stub->Compile(&cntl, &req, &res, nullptr);
                if(cntl.Failed()){
                    errorlog << "编译服务调用失败!";
                    return handler(false,"500 Error!");
                }
                if(res.status() == 0){
                    // 运行成功,比对运行结果
                    if(testcase.output() == res.result()){
                        // 此case运行结果正确
                        num++;
                        continue;
                    }else{
                        std::string ret_msg = std::to_string(num);
                        ret_msg += "/";
                        ret_msg += std::to_string(sum);
                        ret_msg += "运行错误\n";
                        ret_msg += "input:\n";
                        ret_msg += testcase.input();
                        ret_msg += "output:\n";
                        ret_msg += testcase.output();
                        // mysql
                        insert_record_to_mysql(email,"Wrong answer",ret_msg);
                        return handler(true
                            ,"运行结果错误"
                            ,"答案错误"
                            ,ret_msg
                            ,request->code()
                            ,request->language());
                    }
                }else if(res.status() == 1){
                    // 内部错误
                    errorlog << "内存错误（读写文件失败！）";
                    return handler(false,"500 ERROR!");
                }else{
                    std::string stats;
                    if(res.status() == 2){
                        stats = "Compilation Error";
                    }else if(res.status() == 3){
                        stats = "Segmentation Fault";
                    }else if(res.status() == 4){
                        stats = "Floating Point Exception";
                    }
                    insert_record_to_mysql(email, stats,res.err_msg());
                    // 编译或运行报错
                    return handler(false,"编译失败","未通过"
                        ,res.err_msg(),request->code(),request->language());
                }
            }
            insert_record_to_mysql(email, "Accept","");
            std::string msg = std::to_string(sum) + "/" + std::to_string(sum) + " 通过全部用例";
            handler(true
                ,msg
                ,"Accept"
                ,""
                ,request->code()
                ,request->language());
        }
        void SubmissionDetail(::google::protobuf::RpcController* controller,
                        const ::problem::SubmissionDetailRequest* request,
                        ::problem::SubmitResponse* response,
                        ::google::protobuf::Closure* done){
            // 1. 验证token
            // 2. 
            // 3. 构造响应
            brpc::ClosureGuard done_guard(done);
            auto handler = [&](const bool success,const std::string& message=""){
                response->set_success(success);
                response->set_message(message);
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                controller->SetFailed("Invalid token!");
                return handler("Invalid token", "获取提交详情失败，用户未登录");
            }
            std::string email = *opt;
            ProblemSubmissionRecord record;
            if(!_problem_mysql->query_submit_record(request->id(),record)){
                controller->SetFailed("dababase error!");
                return handler(false,"查询失败");
            }
            response->set_accepted(record.accept);
            response->set_result(record.result);
            response->set_time_cost(lyt::RandomValue::random(100.00f,1000.00f));
            response->set_memory_cost(128);
            response->set_code(record.code);
            response->set_language(record.language);
            handler(true,"查询成功!");
        }
        
        void SubmissionList(::google::protobuf::RpcController* controller,
                        const ::problem::SubmissionListRequest* request,
                        ::problem::SubmissionListResponse* response,
                        ::google::protobuf::Closure* done){
            // 1. 验证token
            // 2. 
            // 3. 构造响应
            brpc::ClosureGuard done_guard(done);
            auto handler = [&](const bool success,const std::string& message=""){
                response->set_success(success);
                response->set_message(message);
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                controller->SetFailed("Invalid token!");
                return handler("Invalid token", "获取提交列表失败，用户未登录");
            }
            std::string email = *opt;
            std::vector<problem::SubmitResult> results;
            if(!_problem_mysql->query_submit_records(email, request->problem_id(), request->page()
                                        , request->page_size(), results)){
                controller->SetFailed("odb error!");
                return handler(false,"无法查询对应记录");
            }
            for(const auto& res : results){
                auto sr = response->add_result();
                sr->set_id(res.id());
                sr->set_accepted(res.accepted());
                sr->set_language(res.language());
                sr->set_time(res.time());
            }
            response->set_total(results.size());
            handler(true,"获取提交记录成功!");
        }
        void UserProblemList(::google::protobuf::RpcController* controller,
                        const ::problem::UserProblemListRequest* request,
                        ::problem::UserProblemListResponse* response,
                        ::google::protobuf::Closure* done){
            // 1. 验证token
            // 2. 
            // 3. 构造响应
            brpc::ClosureGuard done_guard(done);
            auto handler = [&](const bool success,const std::string& message=""){
                response->set_success(success);
                response->set_message(message);
            };
            auto build_result = [&](const std::pair<Json::Value,int>& result){
                for(const auto& hit : result.first){
                    const auto& source = hit["source"];
                    auto problem = response->add_problems();
                    problem->set_id(source[es_problem_id_key].asUInt());
                    problem->set_title(source[es_problem_title_key].asString());
                    problem->set_difficulty(source[es_problem_difficulty_key].asUInt());
                    // 通过率通过redis获取
                    float rate = _problem_redis->get_acceptance_rate(source[es_problem_id_key].asUInt());
                    if(rate < 0){
                        rate = _problem_redis->cal_acceptance_rate(source[es_problem_id_key].asUInt());
                    }
                    problem->set_acceptance_rate(rate);
                }
                return handler(true,"获取成功~");
            };
            auto opt = _problem_redis->GetUserId(request->token());   
            if(!opt){
                controller->SetFailed("Invalid token!");
                return handler("Invalid token", "获取题目列表失败，用户未登录");
            }
            std::vector<uint32_t> ids;
            // 先去redis中查询，再拿查询出的id去es中查询
            if(request->solved() == false){
                // 已解决题目
                ids = _problem_redis->get_solved_problems(request->email());
            }else if (request->solved() == true) {
                // 尝试题目
                ids = _problem_redis->get_attempting_problems(request->email());
            }
            lyt::search search_cond(_esclient->getclient());
            search_cond.add_must_terms_integer(es_problem_id_key, ids);
            int offset = (request->page()-1) * request->page_size();
            search_cond.set_from(offset).set_size(request->page_size());

            auto result = search_cond.run(es_problem_index);
            for(const auto& hit : result.first){
                const auto& source = hit["source"];
                auto problem = response->add_problems();
                problem->set_id(source[es_problem_id_key].asUInt());
                problem->set_title(source[es_problem_title_key].asString());
                problem->set_difficulty(source[es_problem_difficulty_key].asUInt());
                // 通过率通过redis获取
                float rate = _problem_redis->get_acceptance_rate(source[es_problem_id_key].asUInt());
                if(rate < 0){
                    rate = _problem_redis->cal_acceptance_rate(source[es_problem_id_key].asUInt());
                }
                problem->set_acceptance_rate(rate);
            }
            return handler(true,"获取成功~");
        }
    };

    class ProblemServer{
    private:
        std::shared_ptr<brpc::Server> _server;
        lyt::Register::ptr _rigister;
        ProblemImpl::ptr _problem;
    public:
        using ptr = std::shared_ptr<ProblemServer>;
        ProblemServer(const ProblemImpl::ptr problem
                    ,const std::shared_ptr<brpc::Server> svr
                    ,const lyt::Register::ptr regis)
                    :_server(svr),_rigister(regis),_problem(problem){}
        void start(){
            _server->RunUntilAskedToQuit();
        }
    };
    class ProblemServerBuild{
    private:
        std::shared_ptr<odb::mysql::database> _odb;
        // redis
        std::shared_ptr<sw::redis::Redis> _redis;
        std::shared_ptr<brpc::Server> _server;
        ProblemImpl::ptr _problem;
        lyt::Register::ptr _register;
        esclient::ptr _esclient;

    public:
        using ptr = std::shared_ptr<ProblemServerBuild>;
        ProblemServerBuild(){}
        ProblemServerBuild& make_odb_client(
                const std::string& user,
                const std::string& passwd,
                const std::string& db_name,
                const std::string& host,
                size_t port,
                const std::string& charset,
                size_t conn_pool_num){
                    _odb = lyt::mysql_build::build(user, passwd, db_name, host, port, charset, conn_pool_num);
                    return *this;
        }
        ProblemServerBuild& make_redis_client(const std::string& host,int port, int db_id,bool keepalive = true) {
            _redis = redis_build::build(host, port, db_id,keepalive);
            return *this;
        }
        ProblemServerBuild& make_elasticsearch_client(const std::vector<std::string>& host){
            _esclient = std::make_shared<esclient>(host);
            return *this;
        }
        ProblemServerBuild& make_problem_service(const std::string& etcd_path){
            if (!_odb) {
                errorlog << "failed to odb uninitizalier";
                abort();
            }
            if (!_redis) {
                errorlog << "failed to redis uninitizalier";
                abort();
            }
            _problem = std::make_shared<ProblemImpl>(_odb,_redis,_esclient,etcd_path);
            return *this;
        }
        ProblemServerBuild& make_brpc_server(const std::size_t& port){
            if(!_problem){
                errorlog << "UserService uninitizalier";
                abort();
            }
            _server = std::make_shared<brpc::Server>();
                if(_server->AddService(&(*_problem),brpc::SERVER_DOESNT_OWN_SERVICE) != 0){
                errorlog << "Failed to add service: UserService";
                abort();
            }
                        brpc::ServerOptions options;
            options.idle_timeout_sec = -1;
            if(_server->Start(port,&options) != 0){
                errorlog << "Failed to start service: UserService";
                abort();
            }
            return *this;
        }
        ProblemServerBuild& make_register(const std::string& host,const std::string& value) {
            if(!_server){
                errorlog << "Can't register to etcd,because UserService not start";
                abort();
            }
            _register = std::make_shared<lyt::Register>(host);
            if(!_register->registory(lyt::problem_path + value, value)){
                errorlog << "Fail register UserService to etcd";
                abort();
            }
            return *this;    
        }
        ProblemServer::ptr build() {
            if (!_server) {
                errorlog << "failed to brpc uninitizalier";
                abort();
            }
            if (!_problem) {
                errorlog << "failed to user uninitizalier";
                abort();
            }
            if (!_register) {
                errorlog << "failed to register uninitizalier";
                abort();
            }
            return std::shared_ptr<ProblemServer>(new ProblemServer(_problem,_server,_register));
        }
    };
}